hope someone can help me on this
I am using the rails serializers to format my model output to a RESTFul JSON web service.
Now my question is...
Say if I have a model user, that I will be using everywhere amongst the application. Some of the places I want pretty much all the user data (name, email, phone, addresses...etc.), while others I just want the name, email. However, these user objects are attachment to some main model via has_many, belongs_to...etc.
So how could I indicate which serializer I want for users at each specific output.
Thanks in advance
In your case, you want custom serializer each case, so use different serializers.
has_many :users, serializer: UserAllSerializer
Also this in case you want to remove the entire key, from the github page of rails serializer.
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body
has_many :comments
def filter(keys)
keys.delete :comments if object.comments_disabled?
keys
end
end
If you want to scope your serialization, take a look at this part
Related
I have a Review model. My users should be able to write reviews. The view for the Review#New should be a form with textfields that the admin creates beforehand.
In other words, my admin-user should be able to create multiple instances of a Review model that has different fields, perhaps even of different input types (string, integer, etc.). That way, when a regular user logs in, they see the different form fields that were specified for data collection by the admin user.
Naturally all of that should be stored in the DB for retrieval within the context it was stored (aka for that specific model).
What's the best way to approach this in Rails?
Think of it like a survey form, and a survey form builder.
It would be good if I could do this with Simple-Form, but that's not a requirement.
Edit 1
Here is an example of the type of fields that they should be able to add to a review:
In my experience a good portion of database design is helped by simply finding the right name for things. In your case I think you are on the right track with thinking about surveys or quizzes.
Check out the survey gem for ideas. In it the base model is Surveys. Surveys have many Questions. Questions have many Options. Surveys also have many Attempts which are answered surveys. Attempts then have many Answers.
So the corollary for you could be to have Reviews/Evaluations (created by admins) which might have many Criteria/Inquiries (possibly of different types, but we'll get to that in a minute). Then your users would create Responses/Assessments which would belong to a specific Review/Evaluation and have many Answers/Responses.
For different question types (Short Answer, Likert Scale Rating, 1-10, Tag List, etc) you could use polymorphism on the criteria/inquiries.
Hopefully some of these names I've used will help you. Feel free to use a thesaurus for more inspiration.
EDIT Re:Polymorphism
Disclaimer: polymorphism might be overkill depending on your application.
Sure, I'll expand some. Not exactly. Take a look at the rails guide on polymorphism if you haven't already. I think what you would want is
class Criterion < ActiveRecord::Base
belongs_to :askable, polymorphic: true
end
Then then I would make a model for each question/criterion type. For example:
class ShortAnswer < ActiveRecord::Base
has_many :criteria, as: :askable
end
class Likert < ActiveRecord::Base
has_many :criteria, as: :askable
end
Side note: If rails does not properly pluralize criterion to criteria you may need to add the following to your config/initializers/inflections.rb file
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'criterion', 'criteria'
end
Scratch solution.
From my experience the easiest solution is to use hstore, json or jsonb type of fields.
This solution play good with Postgresql database.
To achieve this approach you need to add field to your Review model.
Migrations:
# Reviews
def change
add_column :reviews, :structure, :json
end
# Answers
def change
add_column :answers, :values, :hstore
end
Then you can define model ReviewStructure plain ruby class, here you can use Virtus gem to serialize it easely:
class ReviewStructure
include Virtus.model
attribute :fields, Array[Field]
class Field
include Virtus.model
attribute :name
attribute :type
end
end
Then define in Review the serialization for structure field:
class Review < ActiveRecord::Base
...
serialize :structure, ReviewStructure
end
Then you can access structure fields of review with review.structure.fields.
In view you can use a simple form
<% simple_form_for #answer do |f| %>
<% #review.structure.fields.each do |field| %>
<% f.input "values[#{field.name}]", as: field.type %>
<% end %>
<% end %>
To access answer results just use:
answer.values.each do |field_name, value|
...
end
Note:
As for admin for it's better to handle creation of review structure on client side(using js), and post pure JSON structure via API.
With such approach you will have ability to create quizzes with different types of field.
Note:
Please keep in mind that current implementation connect one review to one answer, assuming that the answer model contains all the values of user response.
When you have a rails resource defined rails seems to automatically create a params entry of attributes for that resource. e.g. if my model Lesson has a subject attribute and I post subject=Maths it automatically creates the param[lesson] = { subject: 'Hello' }. The problem I am having is getting nested attributes to appear within this created lesson array.
I'm using mongoid as my backend and have an association on Lesson called activities. The code looks like this:
class Lesson
include Mongoid::Document
field :subject, type: String
embeds_many :activities, class_name: 'LessonActivity' do
def ordered
#target.sort { |x, y| x.display_order <=> y.display_order }
end
def reorder!
#target.each_with_index { |val, index| val.display_order = index }
end
end
accepts_nested_attributes_for :activities
However I can't work out how I access this activities from within params.require(:lesson).permit :activities
I can access it via params.permit(:activities) but that feels a bit messy
I've done some digging and found out what's going on with this.
It all comes from a rails feature, the Param wrapper, details and api. Which configured for json will automatically pass the attributes of the model into a param of the model name (in this case Lesson).
The attributes of the model that will be populated based on how the model responds to the method attribute_names so this gives two routes to achieve the aims of the question.
1 - Instruct my controller to include activities as part of Lesson parameters, e.g. using this method:
class Api::LessonsController < Api::ApiController
wrap_parameters Lesson, include: Lesson.attribute_names << :activities
2 - Update the attiribute_names method for the model to include :activities
I'm still left with a couple of things to resolve, namely the reason associations aren't part of attribute_names on Mongoid and if overriding it to include attribute names is a bad idea.
Basing on the params you provided for your JSON POST request, you will need the following code to whitelist the params you need:
def activities_params
params.require(:activities).permit(:title, :display_order, :content, :time)
end
The params forwarded by your JSON POST request did not have the :activities hash as a value to the :lesson key so whitelisting the params you need is simple like above.
I think you may have answered you question here:
"how I can make it part of lessons key or why I can't. I'm not passing a lesson parameter "
If I read that correctly, you are not passing the lesson param, just a hash of Activities?
That would explain why you can access
params.permit(:activities)
but not
params.require(:lesson).permit :activities
I have a Media model that has a bunch of standard metadata attributes and is persisted in the database as normal. What I want to do now is to add some configurable metadata attributes to this model on top of the existing attributes. A list of these attributes will be defined in a config file and loaded in at runtime. They'll be stored in the database in a different table as a series of property-value pairs with an association to the main model.
So, my code currently is,
class Media < ActiveRecord::Base
has_many :custom_metadata
attr_accessible :title, :language, :copyright, :description
end
and
class CustomMetadata < ActiveRecord::Base
belongs_to :media
attr_accessible :name, :value
end
What I want to do is to be able to access and update the custom metadata attributes on the Media model in the same way as the standard metadata attributes. For example, if the custom metadata attributes are called publisher and contributor, then I want to access them in the Media model as #media.publisher and #media.contributor even though they will be in the association #media.custom_metadata where its values would be something like [{:name => 'publisher', :value => 'Fred'}, {:name => 'contributor', :value => 'Bill'}]
It seems to be that virtual attributes would be the best way of achieving this but all of the examples I can find of people using virtual attributes is where the names of the attributes are static and known rather than dynamic from a run-time configuration, so they can define methods such as publisher and publisher= which would then contain code to write to the relevant associated property-value record.
I can define attributes on the class with attr_accessor *Settings.custom_metadata_fields (assuming Settings.custom_metadata_fields returns [:publisher, :contributor]) and also allow mass-assignment using a similar technique with attr_accessible.
The part I get stuck on is how to populate the virtual attributes from the association when loading the data from the record and then, in reverse, how to pass the data in the virtual attributes back into the association before the record is saved.
The two ways I currently see this working are either using method_missing or attribute_missing, or perhaps via initialize and a before_save callback? In either case, I'm not sure how I would define it given that my model has a mix of normal attributes and virtual attributes.
Any suggestions?
Using callbacks sounds reasonable.
What database are you using? If PostgreSQL, maybe you should take a look at HStore extension (http://www.postgresql.org/docs/9.2/static/hstore.html)
it will perform better, and there are some gems making it easy to use.
After looking into the callbacks some more I discovered the after_initialize callback and this is much better than using the initialize method as I'd first planned.
In the end, this was the final code for the Media model and I didn't change anything in the CustomMetadata model from what I defined in the question,
class Media < ActiveRecord::Base
has_many :custom_metadata
attr_accessor *Settings.custom_metadata_fields
attr_accessible :title, :language, :copyright, :description
attr_accessible *Settings.custom_metadata_fields
validates_presence_of *Settings.required_custom_fields
before_save :save_custom_metadata
after_initialize :load_custom_metadata
def load_custom_metadata
MediaMetadata.custom_all_fields.each do |field|
custom_record = custom_metadata.where(:name => field.to_s).first_or_initialize()
send("#{field}=", custom_record.value)
end
end
def save_custom_metadata
MediaMetadata.custom_all_fields.each do |field|
custom_record = custom_metadata.where(:name => field.to_s).first_or_initialize()
custom_record.value = send(field)
if custom_record.value.blank?
custom_record.destroy
else
custom_record.save
end
end
end
end
This solution had a couple of nice benefits. Firstly, it doesn't affect any of the normal attributes on the Media model. Secondly, only custom metadata with actual values are stored in the custom metadata table. If the value is blank, the record is removed completely. Finally, I can use standard validations on the model attributes as shown for my required custom metadata attributes.
Before I start, let me excuse myself for asking such a basic question, but I really didn't find any suitable information.
So, I have two ActiveRecord Models, Managers and Orders:
class Manager < ActiveRecord::Base
attr_accessible ...
has_many :orders
class Order < ActiveRecord::Base
belongs_to :manager
I have a backbone collection, which perfectly fetches managers. But what I don't get is how to get my manager's orders. Is there a solution for that or should I handle this manually?
If you want to fetch a list of managers, with each manager having a list of their orders in JSON format, I highly recommend the rabl gem. It makes it very easy to set this up and if needed customize what's included in the JSON, whether you're using Backbone, KnockoutJS or something else on the front end.
I have a very simple model
class Lifestyle < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :profiles
end
that has a has_and_belongs_to_many relationship with Profile
class Profile < ActiveRecord::Base
attr_accessible ...
belongs_to :occupation
has_and_belongs_to_many :lifestyles
accepts_nested_attributes_for :lifestyles
end
I want to use ActiveAdmin to edit the Profile object, but also assign Lifestyles to a profile. It should be similar to dealing with belongs_to :occupation, as this is sorted out automatically by ActiveAdmin to a dropbox with the options pre-filled with available occupations.
I've tried to use the has_many form builder method, but that only got me to show a form to type in the name of the Lifestyle and on submission, it returned an error.
f.object.lifestyles.build
f.has_many :lifestyles do |l|
l.input :name
end
Error I get:
Can't mass-assign protected attributes: lifestyles_attributes
The perfect way for me would be to build several checkboxes, one for each Lifestyle in the DB. Selected means that the lifestyle is connected to the profile, and unselected means to delete the relation.
I'm having great doubts that this is possible using ActiveAdmin and without having to create very complex logic to deal with this. I would really appreciate it if you'd give your opinion and advise me if I should go this way or approach it differently.
After some research, I am ready to answer my own question.
First, I have to say thanks to #Lichtamberg for suggesting the fix. However, that only complicates things (also regarding security, though not an issue in this case), and doesn't help me reach my ideal solution.
Digging more, I found out that this is a very common scenario in Rails, and it's actually explained in Ryan Bates' screencast no #17.
Therefore, in Rails, if you have a has_and_belongs_to_many (short form HABTM) association, you can easily set the ids of the other associated object through this method:
profile.lifestyle_ids = [1,2]
And this obviously works for forms if you've set the attr_accessible for lifestyle_ids:
class Profile < ActiveRecord::Base
attr_accessible :lifestyle_ids
end
In ActiveAdmin, because it uses Formtastic, you can use this method to output the correct fields (in this case checkboxes):
f.input :lifestyles, as: :check_boxes, collection: Lifestyle.all
Also, I have simplified my form view so it's now merely this:
form do |f|
f.inputs # Include the default inputs
f.inputs "Lifestlyes" do # Make a panel that holds inputs for lifestyles
f.input :lifestyles, as: :check_boxes, collection: Lifestyle.all # Use formtastic to output my collection of checkboxes
end
f.actions # Include the default actions
end
Ok, now this rendered perfectly in the view, but if I try and submit my changes, it gives me this database error:
PG::Error: ERROR: null value in column "created_at" violates not-null constraint
: INSERT INTO "lifestyles_profiles" ("profile_id", "lifestyle_id") VALUES (2, 1) RETURNING "id"
I found out that this is due to the fact that Rails 3.2 doesn't automatically update the timestamps for a HABTM association table (because they are extra attributes, and Rails only handles the _id attributes.
There are 2 solutions to fix this:
Either convert the association into a hm:t (has_many, :through =>)
Or remove the timestamps from the table
I'm going to go for 2) because I will never need the timestamps or any extra attributes.
I hope this helps other people having the same problems.
Edit: #cdesrosiers was closest to the solution but I already wrote this answer before I read his. Anyway, this is great nevertheless. I'm learning a lot.
Active Admin creates a thin DSL (Domain-Specific Language) over formtastic, so it's best to look at the formastic doc when you need form customization. There, you'll find that you might be able to use f.input :lifestyles, :as => :check_boxes to modify a has_and_belongs_to_many relationship.
I say "might" because I haven't tried this helper myself for your particular case, but these things have a tendency to just work automagically, so try it out.
Also, you probably won't need accepts_nested_attributes_for :lifestyles unless you actually want to modify the attributes of lifestyles from profiles, which I don't think is particularly useful when using active admin (just modify lifestyles directly).
Add
attr_accessible :lifestyles_attributes
f.e.:
class AccountsController < ApplicationController
attr_accessible :first_name, :last_name
end